package cmd

import (
	"context"
	"fmt"
	"strings"

	"github.com/zfd81/rooster/util"

	pb "gitee.com/zfd81/dbrs/proto/dbrspb"

	"github.com/bgentry/speakeasy"

	"github.com/spf13/cobra"
)

var (
	userShowDetail bool
	getPrefix      bool
)

// NewUserCommand returns the cobra cmd for "user".
func NewUserCommand() *cobra.Command {
	ac := &cobra.Command{
		Use:   "user <subcommand>",
		Short: "User related commands",
	}

	ac.AddCommand(newUserAddCommand())
	ac.AddCommand(newUserDeleteCommand())
	ac.AddCommand(newUserListCommand())
	ac.AddCommand(newUserChangePasswordCommand())
	ac.AddCommand(newUserSetDefaultSignCommand())
	return ac
}

func newUserAddCommand() *cobra.Command {
	cmd := cobra.Command{
		Use:   "add <user name or user:password> [options]",
		Short: "Adds a new user",
		Run:   userAddCommandFunc,
	}
	return &cmd
}

func newUserDeleteCommand() *cobra.Command {
	return &cobra.Command{
		Use:   "del <user name>",
		Short: "Deletes a user",
		Run:   userDeleteCommandFunc,
	}
}

func newUserGetCommand() *cobra.Command {
	cmd := cobra.Command{
		Use:   "get <user name> [options]",
		Short: "Gets detailed information of a user",
		Run:   userGetCommandFunc,
	}

	cmd.Flags().BoolVar(&userShowDetail, "detail", false, "Show permissions of roles granted to the user")

	return &cmd
}

func newUserListCommand() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "list [user name]",
		Short: "Lists all users",
		Run:   userListCommandFunc,
	}
	cmd.Flags().BoolVar(&getPrefix, "prefix", false, "List users with matching prefix")
	return cmd
}

func newUserChangePasswordCommand() *cobra.Command {
	cmd := cobra.Command{
		Use:   "pwd <user name> [options]",
		Short: "Changes password of user",
		Run:   userChangePasswordCommandFunc,
	}
	return &cmd
}

func newUserSetDefaultSignCommand() *cobra.Command {
	cmd := cobra.Command{
		Use:   "sign <user name, default sign>",
		Short: "Sets user default signature",
		Run:   userSetDefaultSignCommandFunc,
	}
	return &cmd
}

// userAddCommandFunc executes the "user add" cmd.
func userAddCommandFunc(cmd *cobra.Command, args []string) {
	if len(args) != 1 {
		ExitWithError(ExitBadArgs, fmt.Errorf("user add cmd requires user name as its argument."))
	}

	var password string
	var name string

	splitted := strings.SplitN(args[0], ":", 2)
	if len(splitted) < 2 {
		name = args[0]
		password = readPasswordInteractive(args[0])
	} else {
		name = splitted[0]
		password = splitted[1]
		if len(name) == 0 {
			ExitWithError(ExitBadArgs, fmt.Errorf("empty user name is not allowed."))
		}
	}

	token, err := getToken()
	if err != nil {
		ExitWithError(ExitError, err)
	}

	request := &pb.AddUserRequest{
		Token: token,
		Userinfo: &pb.User{
			Name:     name,
			Password: password,
		},
	}

	for _, endpoint := range globalFlags.Endpoints {
		client, err := GetBrokerSystemClient(endpoint)
		if err != nil {
			ExitWithError(ExitBadConnection, fmt.Errorf("Endpoint %s add user error: %v\n", endpoint, err))
		}
		_, err = client.AddUser(context.Background(), request)
		if err != nil {
			ExitWithError(ExitBadFeature, fmt.Errorf("Endpoint %s add user error: %v\n", endpoint, err))
		}
	}
	fmt.Printf("User %s created.\n", name)
}

// userDeleteCommandFunc executes the "user delete" cmd.
func userDeleteCommandFunc(cmd *cobra.Command, args []string) {
	if len(args) != 1 {
		ExitWithError(ExitBadArgs, fmt.Errorf("user delete cmd requires user name as its argument."))
	}

	token, err := getToken()
	if err != nil {
		ExitWithError(ExitError, err)
	}

	request := &pb.DeleteUserRequest{
		Token: token,
		Userinfo: &pb.User{
			Name: args[0],
		},
	}

	for _, endpoint := range globalFlags.Endpoints {
		client, err := GetBrokerSystemClient(endpoint)
		if err != nil {
			ExitWithError(ExitBadConnection, fmt.Errorf("Endpoint %s delete user error: %v\n", endpoint, err))
		}
		_, err = client.DeleteUser(context.Background(), request)
		if err != nil {
			ExitWithError(ExitBadFeature, fmt.Errorf("Endpoint %s delete user error: %v\n", endpoint, err))
		}
	}
	fmt.Printf("User %s deleted.\n", args[0])
}

// userGetCommandFunc executes the "user get" cmd.
func userGetCommandFunc(cmd *cobra.Command, args []string) {
	if len(args) != 1 {
		ExitWithError(ExitBadArgs, fmt.Errorf("user get cmd requires user name as its argument."))
	}

	//name := args[0]
	//client := mustClientFromCmd(cmd)
	//resp, err := client.Auth.UserGet(context.TODO(), name)
	//if err != nil {
	//	cobrautl.ExitWithError(cobrautl.ExitError, err)
	//}
	//
	//if userShowDetail {
	//	fmt.Printf("User: %s\n", name)
	//	for _, role := range resp.Roles {
	//		fmt.Printf("\n")
	//		roleResp, err := client.Auth.RoleGet(context.TODO(), role)
	//		if err != nil {
	//			cobrautl.ExitWithError(cobrautl.ExitError, err)
	//		}
	//		display.RoleGet(role, *roleResp)
	//	}
	//} else {
	//	display.UserGet(name, *resp)
	//}
}

// userListCommandFunc executes the "user list" cmd.
func userListCommandFunc(cmd *cobra.Command, args []string) {
	if len(args) > 1 {
		ExitWithError(ExitBadArgs, fmt.Errorf("user list cmd requires at most one arguments."))
	}

	token, err := getToken()
	if err != nil {
		ExitWithError(ExitInvalidInput, err)
	}

	request := &pb.FindAllUsersRequest{
		Token:  token,
		Prefix: getPrefix,
	}
	if len(args) == 1 {
		request.Userinfo = &pb.User{
			Name: args[0],
		}
	}

	client, err := BrokerSystemClient()
	if err != nil {
		ExitWithError(ExitBadConnection, err)
	}
	resp, err := client.FindAllUsers(context.Background(), request)
	if err != nil {
		ExitWithError(ExitBadFeature, err)
	}

	fmt.Println("+-----+--------------------+---------------------+---------------------+")
	fmt.Printf("%1s %3s %1s %18s %1s %19s %1s %19s %1s\n", "|", "NUM", "|", util.Rpad("USER NAME", 18, " "), "|", util.Rpad("CREATE TIME", 19, " "), "|", util.Rpad("MODIFY TIME", 19, " "), "|")
	fmt.Println("+-----+--------------------+---------------------+---------------------+")
	for i, user := range resp.Users {
		fmt.Printf("%1s %3s %1s %18s %1s %19s %1s %19s %1s\n", "|", util.Rpad(fmt.Sprintf("%d", i+1), 3, " "), "|", util.Rpad(user.Name, 18, " "), "|", user.CreateTime, "|", user.ModifyTime, "|")
	}
	fmt.Println("+-----+--------------------+---------------------+---------------------+")
}

// userChangePasswordCommandFunc executes the "user passwd" cmd.
func userChangePasswordCommandFunc(cmd *cobra.Command, args []string) {
	if len(args) != 1 {
		ExitWithError(ExitBadArgs, fmt.Errorf("user passwd cmd requires user name as its argument."))
	}

	password := readPasswordInteractive(args[0])

	token, err := getToken()
	if err != nil {
		ExitWithError(ExitError, err)
	}

	request := &pb.ModifyUserRequest{
		Token: token,
		Userinfo: &pb.User{
			Name:     args[0],
			Password: password,
		},
	}

	for _, endpoint := range globalFlags.Endpoints {
		client, err := GetBrokerSystemClient(endpoint)
		if err != nil {
			ExitWithError(ExitBadConnection, fmt.Errorf("Endpoint %s modify user password error: %v\n", endpoint, err))
		}
		_, err = client.ChangePassword(context.Background(), request)
		if err != nil {
			ExitWithError(ExitBadFeature, fmt.Errorf("Endpoint %s modify user password error: %v\n", endpoint, err))
		}
	}
	fmt.Printf("Password updated.\n")
}

func userSetDefaultSignCommandFunc(cmd *cobra.Command, args []string) {
	if len(args) != 2 {
		ExitWithError(ExitBadArgs, fmt.Errorf("user sign cmd requires user name and default signature as its argument."))
	}

	token, err := getToken()
	if err != nil {
		ExitWithError(ExitError, err)
	}

	request := &pb.ModifyUserRequest{
		Token: token,
		Userinfo: &pb.User{
			Name: args[0],
			Sign: args[1],
		},
	}

	for _, endpoint := range globalFlags.Endpoints {
		client, err := GetBrokerSystemClient(endpoint)
		if err != nil {
			ExitWithError(ExitBadConnection, fmt.Errorf("Endpoint %s set user signature error: %v\n", endpoint, err))
		}
		_, err = client.SetUserSignature(context.Background(), request)
		if err != nil {
			ExitWithError(ExitBadFeature, fmt.Errorf("Endpoint %s set user signature error: %v\n", endpoint, err))
		}
	}
	fmt.Printf("User %s signed.\n", args[0])
}

func readPasswordInteractive(name string) string {
	prompt1 := fmt.Sprintf("Password of %s: ", name)
	password1, err1 := speakeasy.Ask(prompt1)
	if err1 != nil {
		ExitWithError(ExitBadArgs, fmt.Errorf("failed to ask password: %s", err1))
	}

	if len(password1) == 0 {
		ExitWithError(ExitBadArgs, fmt.Errorf("empty password"))
	}

	prompt2 := fmt.Sprintf("Type password of %s again for confirmation: ", name)
	password2, err2 := speakeasy.Ask(prompt2)
	if err2 != nil {
		ExitWithError(ExitBadArgs, fmt.Errorf("failed to ask password: %s", err2))
	}

	if password1 != password2 {
		ExitWithError(ExitBadArgs, fmt.Errorf("given passwords are different"))
	}

	return password1
}
